﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using TangoGames.Scene;

namespace TangoGames.Base
{
    /// <summary>
    /// Classe para criar câmeras básicas
    /// </summary>
    public class BasicCamera:GameComponent,ICamera
    {
        #region Fields & Properties

        Vector3 position;
        /// <summary>
        /// Representa a posição da câmera, onde ela está.
        /// </summary>
        public Vector3 Position { get { return position; } set { position = value; } }

        Vector3 target;
        /// <summary>
        /// Representa o alvo da câmera, para onde ela aponta.
        /// </summary>
        public Vector3 Target { get { return target; } set { target = value; } }

        Vector3 orientation;
        /// <summary>
        /// Representa a orientação da câmera, qual o seu "cima".
        /// </summary>
        public Vector3 Orientation { get { return orientation; } set { orientation = value; } }

        float apertureAngle;
        /// <summary>
        /// Representa o ângulo de abertura da "lente" da câmera.
        /// </summary>
        public float ApertureAngle { get { return apertureAngle; } set { apertureAngle = value; } }

        float screenRatio;
        /// <summary>
        /// Representa a razão / proporção da tela / monitor (4/3, 16/9...).
        /// </summary>
        public float ScreenRatio { get { return screenRatio; } set { screenRatio = value; } }

        float nearPlane;
        /// <summary>
        /// Representa o plano de corte de proximidade, mais perto que isso a câmera não mostra
        /// </summary>
        public float NearPlane { get { return nearPlane; } set { nearPlane = value; } }

        float farPlane;
        /// <summary>
        /// Representa o plano de corte de distância, mais longe que isso a câmera não mostra
        /// </summary>
        public float FarPlane { get { return farPlane; } set { farPlane = value; } }

        Matrix viewMatrix;
        /// <summary>
        /// Representa a matriz de visualização da câmera
        /// </summary>
        public Matrix ViewMatrix { get { return viewMatrix; } set { viewMatrix = value; } }

        Matrix projectionMatrix;
        /// <summary>
        /// Representa a matriz de projeção da câmera
        /// </summary>
        public Matrix ProjectionMatrix { get { return projectionMatrix; } set { projectionMatrix = value; } }

        private Quaternion viewQuaternion;
        /// <summary>
        /// Orientacao de direção da visão da camera
        /// </summary>
        public Quaternion ViewQuaternion { get { return viewQuaternion; } set { viewQuaternion = value; } }

        private Vector3 xAxis;
        /// <summary>
        /// Vetor base de referencia X
        /// </summary>
        public Vector3 XAxis { get { return xAxis; }  }

        private Vector3 yAxis;
        /// <summary>
        /// Vetor base de referencia Y
        /// </summary>
        public Vector3 YAxis { get { return yAxis; } }

        private Vector3 zAxis;
        /// <summary>
        /// Vetor base de referencia Z
        /// </summary>
        public Vector3 ZAxis { get { return zAxis; } }

        private float angleDegreesX;
        /// <summary>
        /// Angulo deslocamento horizontal
        /// </summary>
        public float AngleDegreesX { get { return angleDegreesX; } }

        private float angleDegreesY;
        /// <summary>
        /// Angulo deslocamento vertical
        /// </summary>
        public float AngleDegreesY { get { return angleDegreesY; } }

        /// <summary>
        /// Lista de câmeras
        /// </summary>
        public static List<BasicCamera> listCameras = new List<BasicCamera>();

        
        private float angle;
        /// <summary>
        /// angulo para uso na rotação
        /// </summary>
        public float Angle
        {
            get { return angle; }
            set {
                angle = value;
                Console.WriteLine("posição anterior = " + Position);
                //Position = new Vector3((float)(Position.X + 100 * Math.Sin(angle)), Position.Y, (float)(Position.Z + 100 * Math.Cos(angle)));
                LookAt(new Vector3((float)(Target.X + 100 * Math.Sin(angle)), Position.Y, (float)(Target.Z + 100 * Math.Cos(angle))), Target, Orientation);
                //LookAt(new Vector3((float)(Target.X + Position.X + 100 * Math.Sin(angle)), Position.Y, (float)(Target.Z +(Position.Z-100) + 100 * Math.Cos(angle))), Target, Orientation);
                //LookAt(new Vector3((float)(Position.X + Target.X + 100 * Math.Sin(angle)), Position.Y, (float)(Target.Z + (Position.Z - 100) + 100 * Math.Cos(angle))), new Vector3(Target.X + Position.X, Target.Y, Target.Z + (Position.Z - 100)), Orientation);
                Console.WriteLine("posição posterior = " + Position);
                Console.WriteLine("target = " + Target);
            }
        }

        /// <summary>
        /// Vetores Normais 
        /// </summary>
        public static Vector3 WORLD_X_AXIS = new Vector3(1.0f, 0.0f, 0.0f);
        public static Vector3 WORLD_Y_AXIS = new Vector3(0.0f, 1.0f, 0.0f);
        public static Vector3 WORLD_Z_AXIS = new Vector3(0.0f, 0.0f, 1.0f);

        #endregion

        #region Construction

        /// <summary>
        /// Construtor padrão com valores padrões
        /// </summary>
        public BasicCamera(Game game):base(game)
        {
            Position = new Vector3(0.0f, 0.0f, 0.0f);
            Target = new Vector3(0,0,-1.0f);
            Orientation = Vector3.Up;

            ApertureAngle = 45.0f;
            ScreenRatio = game.GraphicsDevice.Viewport.AspectRatio;
            NearPlane = 0.1f;
            FarPlane = 500.0f;

            LookAt(position, target, orientation);

            UpdateViewFromvQuaternion(); 

            ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(ApertureAngle),
                ScreenRatio,
                NearPlane,
                FarPlane);

            //game.Components.Add(this);

            //this.UpdateOrder = 10001;

        }

        #endregion

        #region Update

        public override void Update(GameTime gameTime)
        {

            #region a câmera tem que ser sempre atualizada


             UpdateViewFromvQuaternion();

             //ViewMatrix = Matrix.CreateLookAt(
             //    Position,
             //    Target,
             //    Orientation);

            ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(ApertureAngle),
                ScreenRatio,
                NearPlane,
                FarPlane);

            #endregion

        }

        #endregion

        #region Methods 

        public void LookMove(float deltaX, float deltaY)
        {
            deltaX *= -0.02f;
            deltaY *= -0.02f;

            float angleX = MathHelper.ToRadians(deltaX);
            float angleY = MathHelper.ToRadians(deltaY);
            Quaternion rotation = Quaternion.Identity;


            // Rotate the camera about the world Y axis.
            if (angleX != 0.0f)
            {
                Quaternion.CreateFromAxisAngle(ref WORLD_Y_AXIS, angleX, out rotation);
                Quaternion.Concatenate(ref rotation, ref viewQuaternion, out viewQuaternion);
            }

            // Rotate the camera about its local X axis.
            if (angleY != 0.0f)
            {
                Quaternion.CreateFromAxisAngle(ref WORLD_X_AXIS, angleY, out rotation);
                Quaternion.Concatenate(ref viewQuaternion, ref rotation, out viewQuaternion);
            }

        }

        public void LookAt(Vector3 target)
        {
            LookAt(position, target, yAxis);
        }

        public void LookAt(Vector3 position, Vector3 target, Vector3 oriention)
        {
            this.position = position;
            this.target = target;

            zAxis = position - target;
            zAxis.Normalize();

            Vector3.Cross(ref oriention, ref zAxis, out xAxis);
            xAxis.Normalize();

            Vector3.Cross(ref zAxis, ref xAxis, out yAxis);
            yAxis.Normalize();

            viewMatrix.M11 = xAxis.X;
            viewMatrix.M21 = xAxis.Y;
            viewMatrix.M31 = xAxis.Z;

            Vector3.Dot(ref xAxis, ref position, out viewMatrix.M41);
            viewMatrix.M41 = -viewMatrix.M41;

            viewMatrix.M12 = yAxis.X;
            viewMatrix.M22 = yAxis.Y;
            viewMatrix.M32 = yAxis.Z;
            Vector3.Dot(ref yAxis, ref position, out viewMatrix.M42);
            viewMatrix.M42 = -viewMatrix.M42;

            viewMatrix.M13 = zAxis.X;
            viewMatrix.M23 = zAxis.Y;
            viewMatrix.M33 = zAxis.Z;
            Vector3.Dot(ref zAxis, ref position, out viewMatrix.M43);
            viewMatrix.M43 = -viewMatrix.M43;

            viewMatrix.M14 = 0.0f;
            viewMatrix.M24 = 0.0f;
            viewMatrix.M34 = 0.0f;
            viewMatrix.M44 = 1.0f;

            Quaternion.CreateFromRotationMatrix(ref viewMatrix, out viewQuaternion);

        }

        public void UpdateViewFromvQuaternion()
        {
            Matrix.CreateFromQuaternion(ref viewQuaternion, out viewMatrix);

            xAxis.X = viewMatrix.M11;
            xAxis.Y = viewMatrix.M21;
            xAxis.Z = viewMatrix.M31;

            yAxis.X = viewMatrix.M12;
            yAxis.Y = viewMatrix.M22;
            yAxis.Z = viewMatrix.M32;

            zAxis.X = viewMatrix.M13;
            zAxis.Y = viewMatrix.M23;
            zAxis.Z = viewMatrix.M33;

            viewMatrix.M41 = -Vector3.Dot(xAxis, position);
            viewMatrix.M42 = -Vector3.Dot(yAxis, position);
            viewMatrix.M43 = -Vector3.Dot(zAxis, position);

            angleDegreesY = MathHelper.ToDegrees((float)Math.Asin(-viewMatrix.M23));
            angleDegreesX = MathHelper.ToDegrees((float)Math.Atan2(viewMatrix.M13, viewMatrix.M33));

        }

        #endregion

    }
}
